﻿#region using

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

#endregion

namespace JupiterLibrary
{
    /// <summary>
    /// Un nodo es un elemento que forma parte de la escena, y contiene la información relativa al SceneGraph
    /// </summary>
    public class Node
    {

        Node parent;

        public Node Parent { get { return parent; } set { MoveParent(value);} }

        /// <summary>
        /// World Matrix que se utiliza para renderizar
        /// </summary>
        public Matrix World;

        /// <summary>
        /// Local Matrix, que es la que se aplica primero localmente al modelo
        /// </summary>
        public Matrix Local;

        /// <summary>
        /// Angulos de rotación que se aplican a la matriz local. si es en radianes o en grados, depende de <see cref="RotationInDegrees"/>. Por defecto es en grados
        /// </summary>
        public Vector3 RotationAngles;

        /// <summary>
        /// Factor de escala que se aplica a la matriz local
        /// </summary>
        public Vector3 Scale=Vector3.One;

        /// <summary>
        /// Posición que se aplicará a la matriz local
        /// </summary>
        public Vector3 Position;

        public IRenderObject RenderObject;

        public List<Node> Children = new List<Node>();

        /// <summary>
        /// Indica si usaremos grados para la rotación en lugar de radianes
        /// </summary>
        public bool RotationInDegrees = true;

        Matrix scaleMatrix;
        Matrix translationMatrix;
        Quaternion rotationQuaternion;
        Matrix rotationMatrix;

        public Node(Node parentNode)
        {
            // Lo asigno con la propiedad y no con el campo , pq de esta forma se añadirá a la lista de Children

            Parent = parentNode;
        }

        /// <summary>
        /// Actualiza la matriz local
        /// </summary>
        protected virtual void UpdateLocal()
        {
            translationMatrix = Matrix.CreateTranslation(Position);

            scaleMatrix = Matrix.CreateScale(Scale);

            if(RotationInDegrees)
                
                rotationQuaternion = Quaternion.CreateFromYawPitchRoll(MathHelper.ToRadians(RotationAngles.Y), MathHelper.ToRadians(RotationAngles.X), MathHelper.ToRadians(RotationAngles.Z));
            else
                rotationQuaternion = Quaternion.CreateFromYawPitchRoll(RotationAngles.Y, RotationAngles.X, RotationAngles.Z);

            rotationMatrix = Matrix.CreateFromQuaternion(rotationQuaternion);

            Local = scaleMatrix * rotationMatrix * translationMatrix;

        }


        /// <summary>
        /// Actualiza la matriz World
        /// </summary>
        public void Update()
        {
            UpdateLocal();

            if (parent != null)
            {
                World = Local * parent.World;
            }
            else
            {
                World = Local;
            }

            if (RenderObject != null) RenderObject.Update(this);

            // Actualizamos los hijos

            foreach (Node node in Children)
            {
                node.Update();
            }
        }

        /// <summary>
        /// Renderiza
        /// </summary>
        public void Draw()
        {
            if (RenderObject != null) RenderObject.Draw(this);

            // Dibujamos los hijos

            foreach (Node node in Children)
            {
                node.Draw();
            }
        }

        void MoveParent(Node destination)
        {

            if(parent==destination) return;

            // Elimino de la lista de hijos a este

            if (parent != null) parent.Children.Remove(this);

            // Lo añado a la lista que corresponde

            destination.Children.Add(this);

            parent=destination;
        }
    }
}
